/* Copyright Thanh-Trung Pham, 2015 */
package jp.ac.uaizu.control;

import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpServer;
import jp.ac.jaist.control.RMPIController;
import jp.ac.jaist.input.RMPIIAWrapper;
import jp.ac.jaist.input.RMPIInputModeler;
import jp.ac.uaizu.generation.ISerCompGenerator;
import jp.ac.uaizu.generation.OutputModel;
import jp.ac.uaizu.generation.SerCompGenerators;
import jp.ac.uaizu.message.SerCompMediatorException;
import jp.ac.uaizu.message.SerCompMessageResource;
import jp.ac.uaizu.message.SerCompMessageUtility;
import jp.ac.uaizu.translation.ISerCompTranslator;
import jp.ac.uaizu.translation.InputModel;
import jp.ac.uaizu.translation.SerCompTranslators;

import javax.jws.WebService;
import javax.xml.ws.Endpoint;
import javax.xml.ws.WebServiceException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.UUID;

/**
 * @author Thanh-Trung Pham
 */
@WebService(endpointInterface = "jp.ac.uaizu.control.ISerCompMediator")
public class SerCompMediator implements ISerCompMediator {

    private static final String WS_RMPISERCOMP = "/ws/rmpisercomp";
    private static final int NUMBER_OF_PREDICTION_WORKERS = 3;

    private SerCompTranslators m_oSerCompTranslators;
    private SerCompGenerators m_oSerCompGenerators;

    private HttpServer m_oHTTPServer;
    SerCompPredictionHelper m_oSerCompPredictionHelper;

    /**
     * The constructor.
     *
     * @throws WebServiceException if there is an error.
     */
    public SerCompMediator() throws WebServiceException {
        try {
            SerCompSettings oSerCompSettings = SerCompSettings.getInstance();

            m_oSerCompTranslators = new SerCompTranslators(oSerCompSettings.getTranslatorsFolderName());
            m_oSerCompGenerators = new SerCompGenerators(oSerCompSettings.getGeneratorsFolderName());

            m_oSerCompPredictionHelper = new SerCompPredictionHelper(NUMBER_OF_PREDICTION_WORKERS);

        } catch (Exception oEx) {
            throw new WebServiceException(oEx);
        }
    }


    @Override
    public byte[][] predictServiceReliability(String translatorName, String generatorName, String configurationSettings, byte[] reliabilityRequirements, byte[] usageProfiles, byte[] compositionWorkflow, byte[] failureModels) throws WebServiceException {
        try {
            ISerCompTranslator oTranslator = m_oSerCompTranslators.getTranslator(translatorName);
            InputModel oInputModel = oTranslator.translate(configurationSettings, reliabilityRequirements, usageProfiles, compositionWorkflow, failureModels);

            RMPIInputModeler oRMPIInputModeler = adjustAndValidateModel(oInputModel, true);
            SerCompPredictionWork oPredictionWork = new SerCompPredictionWork(null, oRMPIInputModeler, oInputModel);
            m_oSerCompPredictionHelper.runPrediction(oPredictionWork);

            checkAndProcessResults(oRMPIInputModeler, oPredictionWork.getRMPIIAWrapper());

            ISerCompGenerator oGenerator = m_oSerCompGenerators.getGenerator(generatorName);
            return oGenerator.generate(reliabilityRequirements, new OutputModel(oPredictionWork.getRMPIIAWrapper(), oRMPIInputModeler));
        } catch (NoClassDefFoundError oErr) {
            throw new WebServiceException(oErr);
        } catch (Exception oEx) {
            throw new WebServiceException(oEx);
        }
    }

    @Override
    public String predictServiceReliabilityAsync(String translatorName, String configurationSettings, byte[] usageProfiles, byte[] compositionWorkflow, byte[] failureModels) throws WebServiceException {
        try {
            ISerCompTranslator oTranslator = m_oSerCompTranslators.getTranslator(translatorName);
            InputModel oInputModel = oTranslator.translate(configurationSettings, null, usageProfiles, compositionWorkflow, failureModels);

            RMPIInputModeler oRMPIInputModeler = adjustAndValidateModel(oInputModel, false);

            String strPredictionID = UUID.randomUUID().toString();
            SerCompPredictionWork oPredictionWork = new SerCompPredictionWork(strPredictionID, oRMPIInputModeler, oInputModel);
            m_oSerCompPredictionHelper.startPrediction(oPredictionWork);

            return strPredictionID;
        } catch (NoClassDefFoundError oErr) {
            throw new WebServiceException(oErr);
        } catch (Exception oEx) {
            throw new WebServiceException(oEx);
        }
    }

    @Override
    public Integer getPredictionState(String predictionID) throws WebServiceException {
        SerCompPredictionWork oPredictionWork = m_oSerCompPredictionHelper.getPredictionWorkFromWorkCache(predictionID);
        if (oPredictionWork == null) {
            return null;
        }

        return oPredictionWork.getState();
    }

    @Override
    public void removePrediction(String predictionID) {
        m_oSerCompPredictionHelper.removePredictionWork(predictionID);
    }

    @Override
    public void removeAllPredictions() {
        m_oSerCompPredictionHelper.removeAllPredictionWorks();

    }

    @Override
    public String[] getAllPredictionIDs() {
        return m_oSerCompPredictionHelper.getAllPredictionIDs();
    }

    @Override
    public byte[][] getPredictionFeedback(String predictionID, String generatorName, byte[] reliabilityRequirements) throws WebServiceException {
        SerCompPredictionWork oPredictionWork = m_oSerCompPredictionHelper.getPredictionWorkFromWorkCache(predictionID);
        if (oPredictionWork == null || oPredictionWork.getState() != SerCompPredictionWork.COMPLETED_STATE) {
            return null;
        }

        try {
            RMPIInputModeler oRMPIInputModeler = oPredictionWork.getRMPIInputModeler();
            checkAndProcessResults(oRMPIInputModeler, oPredictionWork.getRMPIIAWrapper());

            ISerCompGenerator oGenerator = m_oSerCompGenerators.getGenerator(generatorName);
            return oGenerator.generate(reliabilityRequirements, new OutputModel(oPredictionWork.getRMPIIAWrapper(), oRMPIInputModeler));
        } catch (NoClassDefFoundError oErr) {
            throw new WebServiceException(oErr);
        } catch (Exception oEx) {
            throw new WebServiceException(oEx);
        }

    }

    /**
     * Check and process the prediction results before delivering to a feedback generator plug-in.
     *
     * @param rmpiInputModeler    the RMPI Modeller.
     * @param resultRMPIIAWrapper the resulting RMPI IA Wrapper containing prediction results.
     * @throws SerCompMediatorException if there is an error.
     */
    private void checkAndProcessResults(RMPIInputModeler rmpiInputModeler, RMPIIAWrapper resultRMPIIAWrapper) throws SerCompMediatorException {
        if (!isValidIA(resultRMPIIAWrapper, rmpiInputModeler)) {
            throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_INVALID_PREDICTION_RESULTS));
        }
    }

    /**
     * Adjust and validate the input provided by the input translator plug-in.
     *
     * @param inputModel the input provided by the input translator plug-in.
     * @param isSync     true if the prediction is in synchronous mode, otherwise false.
     * @return the RMPI Input Modeler for a later usage.
     * @throws SerCompMediatorException if there is an error from the input provided by the input translator plug-in.
     */
    private RMPIInputModeler adjustAndValidateModel(InputModel inputModel, boolean isSync) throws SerCompMediatorException {
        if (isSync) {
            RunConfigurationSettings oRunSettings = inputModel.getRunConfigurationSettings();
            if (oRunSettings.getNumberOfSimulationsRequired() > 0) {
                throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_UNSUPPORTED_PREDICTION_METHOD_IN_SYNC_MODE));
            }
        }

        StringBuilder oOutputContent = new StringBuilder();

        RMPIController oRMPIController = RMPIController.newInstance();
        RMPIInputModeler oRMPIInputModeler = oRMPIController.validateInputModelFromString(inputModel.getRMPIInputModel(), oOutputContent);
        if (!oRMPIInputModeler.isValid()) {
            throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_INVALID_TRANSLATED_INPUT_MODEL, oOutputContent.toString()));
        }

        return oRMPIInputModeler;
    }

    /**
     * Check if an IA wrapper is valid.
     *
     * @param iaWrapper the IA wrapper.
     * @param modeler   the RMPI modeler.
     * @return true if an IA wrapper is valid, otherwise false.
     */
    private boolean isValidIA(RMPIIAWrapper iaWrapper, RMPIInputModeler modeler) {
        Set<Set<String>> oAIOS = modeler.getAIOS();
        Set<Set<String>> oAFS = modeler.getAFS();

        double dTotalProbability = 0.0;
        for (Set<String> oOutputSet : oAIOS) {
            dTotalProbability += iaWrapper.getProbability(RMPIInputModeler.RMPI_F0_AS_SET, oOutputSet);
        }

        for (Set<String> oSignaledFailures : oAFS) {
            dTotalProbability += iaWrapper.getProbability(RMPIInputModeler.RMPI_F0_AS_SET, oSignaledFailures);
        }

        double dEpsilon = 0.00000000001;
        if (dTotalProbability < (1.0 - dEpsilon) || dTotalProbability > (1.0 + dEpsilon)) {
            return false;
        }


        return true;
    }


    /**
     * Publish as a SOAP-based web service.
     *
     * @return the URL of the WSDL of the web service.
     * @throws SerCompMediatorException if there is an error.
     */
    public String publishAsWebService() throws SerCompMediatorException {
        try {
            if (m_oHTTPServer != null) {
                throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_ALREADY_PUBLISHED));
            }

            SerCompSettings oSerCompSettings = SerCompSettings.getInstance();
            m_oHTTPServer = HttpServer.create(new InetSocketAddress(oSerCompSettings.getListeningPort()), 0);
            HttpContext oHTTPContext = m_oHTTPServer.createContext(WS_RMPISERCOMP);
            Endpoint oEndpoint = Endpoint.create(this);
            oEndpoint.publish(oHTTPContext);

            m_oHTTPServer.setExecutor(null);
            m_oHTTPServer.start();

            //return "http://" + getLocalHostIPAddress() + ":" + oSerCompSettings.getListeningPort() + WS_RMPISERCOMP + "?wsdl";
            return "http://localhost:" + oSerCompSettings.getListeningPort() + WS_RMPISERCOMP + "?wsdl";
        } catch (IOException oIOEx) {
            throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_UNEXPECTED_EXCEPTION, oIOEx.getMessage()));
        }
    }


    /**
     * Get the IP address of the local host.
     *
     * @return the IP address of the local host.
     * @throws SerCompMediatorException if there is an error.
     */
    private String getLocalHostIPAddress() throws SerCompMediatorException {
        try {
            byte[] oIPAddress = InetAddress.getLocalHost().getAddress();

            String strIPAddress = "";
            for (int i = 0; i < oIPAddress.length; i++) {
                if (i > 0) {
                    strIPAddress += ".";
                }
                strIPAddress += oIPAddress[i] & 0xFF;
            }

            return strIPAddress;
        } catch (UnknownHostException oEx) {
            throw new SerCompMediatorException(SerCompMessageUtility.getResourceMessage(SerCompMessageResource.MEDIATOR_UNEXPECTED_EXCEPTION, oEx.getMessage()));
        }
    }

    /**
     * Unpublish the SOAP-based web service.
     */
    public void unpublish() {
        if (m_oHTTPServer != null) {
            m_oHTTPServer.stop(0);
            m_oHTTPServer = null;
        }
    }

    /**
     * Start the RMPISerComp as a standalone app.
     *
     * @throws SerCompMediatorException if there is an error.
     */
    private void startAsStandaloneApp() throws SerCompMediatorException {
        String strURL = publishAsWebService();

        SerCompSettings oSerCompSettings = SerCompSettings.getInstance();
        System.out.println("RMPISerComp's web service is available at " + strURL + ".\n"
                + "Translators can be deployed in folder '" + oSerCompSettings.getTranslatorsFolderName() + "'.\n"
                + "Generators can be deployed in folder '" + oSerCompSettings.getGeneratorsFolderName() + "'.\n");
    }

    /**
     * The main method.
     *
     * @param args an array of arguments
     * @throws Exception if there is an error.
     */
    public static void main(String[] args) throws Exception {
        SerCompMediator oSerCompMediator = new SerCompMediator();
        oSerCompMediator.startAsStandaloneApp();
    }
}
